import std.socket, std.socketstream;
import std.exception, std.string;

debug(all)
{
	import std.stdio;
}

static import std.regexp;

pure nothrow string callback(string name)
{
	return "void "~name~"( ref IRCClient client, string caller, string target, string line )";
}

struct IRCClient
{
	private:
		static struct Pair
		{
			funcCallback callback;
			std.regexp.RegExp regexp;			
		}

		Pair[] callbacks;

		TcpSocket sock;
		SocketStream stream;
		bool connected = false;

		void sendCmd( string cmd )
		{
			debug(all)
			{
				writeln(cmd);
			}
			stream.writeString(cmd);
			stream.writeString("\n");
		}
	public:
		alias void function( ref IRCClient, string, string, string) funcCallback;

		void connect( string server, ushort port, string nick, string password )
		{
			sock = new TcpSocket( new InternetAddress( server, port ) );
			stream = new SocketStream( sock );

			sendCmd("NICK "~nick);
			sendCmd("USER SkyNet localhost localhost :Bot");
			sendPrivMsg( "NickServ", "IDENTIFY "~password);

			connected = true;
		}



		void disconnect()
		{
			connected = false; 

			sendCmd("QUIT :Bye!");

			stream.flush();
			clear(stream);
			sock.close();
			clear(sock); 
		}

		void addHandler( string regexp, funcCallback callback )
		{
			callbacks ~= Pair( callback, std.regexp.RegExp( regexp, "g" ) );
		}

		void run()
		{
			enforce(connected, "Attempt to start working loop on not connected IRC Client");			

			while ( connected && !stream.eof() )
			{
				auto line = stream.readLine().idup;
				if ( line.length == 0 )
					break;
			
				debug(all)
				{
					writeln( line );
				}

				// Start processing

				// Keeping alive
				if ( line[0..6] == "PING :")
				{
					sendCmd( "PONG :"~line[6..$] );
				}

				// Some IRC-related stuff?

					// ...

				// Processing custom callbacks

				auto rgxp_privmsg = std.regexp.RegExp( ":([^!]+)![^ ]+ PRIVMSG ([^ ]+) :(.*)" );
				auto matches = rgxp_privmsg.match( line );

				if ( matches.length != 4 )
					continue;

				line = matches[3];
				auto caller = matches[1];
				auto target = matches[2];

				foreach ( pair; callbacks )
				{
					if ( pair.regexp.test( line ) )
					{
						pair.callback( this, caller, target, line );
						break;
					}
				}
			} 
		}

		// "API"

		void join( string[] channels )
		{
			enforce( connected, "Attempt to join channels on not connected IRC Client");
			foreach (s; channels)
			{
				sendCmd("JOIN "~s);
			}
		}	

		void sendPrivMsg( string target, string msg )
		{
			sendCmd("PRIVMSG "~target~" :"~msg);
		}
}
